import * as THREE from '../../lib/three.js-r128/build/three.module.js';
import {PointerLockControls} from '../../lib/three.js-r128/examples/jsm/controls/PointerLockControls';
import {OBJLoader} from '../../lib/three.js-r128/examples/jsm/loaders/OBJLoader';
import Stats from '../../lib/stats/stats.module.js';

import './css/index.css';

let camera, scene, renderer, controls;

const resourcePath = 'http://localhost:3000/public/'
const resourcesAudioPath = resourcePath + 'audio/';
const resourcesModelPath = resourcePath + 'model/';

const targets = new THREE.Group();
const clock = new THREE.Clock();
const stats = new Stats();
const tan60 = Math.tan(THREE.Math.degToRad(60));
const velocity = new THREE.Vector3();
const direction = new THREE.Vector3();
const vertex = new THREE.Vector3();
const objLoader = new OBJLoader()
const audioLoader = new THREE.AudioLoader();
const listener = new THREE.AudioListener();

const config = {
    camera: {
        yHeight: 1.8,
        jumpingAcceleration: 150,
        gravity: 9.8,
        mass: 50
    },
    playground: {
        playgroundWidth: 72,
        playgroundDepth: 72 * tan60,
        playgroundColors: [
            new THREE.Color(0xFF6633),
            new THREE.Color(0xFF9933),
            new THREE.Color(0xFFCC33),
            new THREE.Color(0xFFFF33)
        ]
    },
    keyboardRecord: {
        moveForward: false,
        moveBackward: false,
        moveLeft: false,
        moveRight: false,
        canJump: false
    },
    targetGround: {
        targetWidth: 72,
        targetHeight: 60,
        targetDepth: 100,
    }
}

const quarterLength = config.playground.playgroundDepth / 4;
const scoreComponent = document.getElementById('score');
const timeComponent = document.getElementById('time');
const fireComponent = document.getElementById('fire');

let resourcesNum = 3;
let resourcesNumLoaded = 0;
let pistolMesh;
let pistolAudio;
let targetAudioBuffer;
let target;
let score = 0;
let fireTimes = 0;

let initBasic = function () {
    camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000);
    camera.position.y = config.camera.yHeight;
    camera.add(listener);

    scene = new THREE.Scene();
    scene.background = new THREE.Color(0xffffff);
    scene.fog = new THREE.Fog(0xffffff, 0, 750);

    renderer = new THREE.WebGLRenderer({antialias: true});
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    document.body.appendChild(stats.dom);

    window.addEventListener('resize', onWindowResize);
}


let initScene = function () {
    const light = new THREE.HemisphereLight(0xeeeeff, 0x777788, 0.75);
    light.position.set(0.5, 1, 0.75);
    scene.add(light);

    // scene.add(new THREE.AxesHelper(100));
    const color1 = config.playground.playgroundColors[0];
    const color2 = config.playground.playgroundColors[1];
    const color3 = config.playground.playgroundColors[2];
    const color4 = config.playground.playgroundColors[3];
    let colorsFloor = [];

    let floorGeometry = new THREE.PlaneGeometry(config.playground.playgroundWidth, config.playground.playgroundDepth, 1, 100);
    floorGeometry.rotateX(-Math.PI / 2);

    let positionAttribute = floorGeometry.attributes.position;

    for (let i = 0, l = positionAttribute.count; i < l; i++) {
        vertex.fromBufferAttribute(positionAttribute, i);
        if (vertex.z > 0) {
            positionAttribute.name = 'block2';
            if (vertex.z > quarterLength) positionAttribute.name = 'block1';
        } else {
            positionAttribute.name = 'block3';
            if (vertex.z < -quarterLength) positionAttribute.name = 'block4';
        }

        switch (positionAttribute.name) {
            case 'block1':
                colorsFloor.push(color1.r, color1.g, color1.b);
                break;
            case 'block2':
                colorsFloor.push(color2.r, color2.g, color2.b);
                break;
            case 'block3':
                colorsFloor.push(color3.r, color3.g, color3.b);
                break;
            case 'block4':
                colorsFloor.push(color4.r, color4.g, color4.b);
                break;
        }
    }

    floorGeometry.setAttribute('color', new THREE.Float32BufferAttribute(colorsFloor, 3));
    const floorMaterial = new THREE.MeshBasicMaterial({vertexColors: true});
    const floor = new THREE.Mesh(floorGeometry, floorMaterial);
    scene.add(floor);

    scene.add(targets);
}

/**
 * 初始化资源, 从指定位置加载资源
 */
let initResources = function () {
    audioLoader.setPath(resourcesAudioPath);
    objLoader.setPath(resourcesModelPath);
    objLoader.load('pistol.obj', function (object) {
        resourcesNumLoaded++;
        pistolMesh = object;
        pistolMesh.rotation.set(-Math.PI / 2, 0, Math.PI / 2)
        pistolMesh.position.set(1.370, -1.040, -2.560)
        pistolMesh.scale.set(.06, .06, .06)
        camera.add(pistolMesh);
        audioLoader.load('pistolShot.wav', function (buffer) {
            resourcesNumLoaded++;
            pistolAudio = new THREE.PositionalAudio(listener);
            pistolAudio.setBuffer(buffer);
            pistolAudio.setVolume(5);
            pistolMesh.add(pistolAudio)
            afterResourcesEnter();
        });
    });
    audioLoader.load('targetAudio.wav', function (buffer) {
        resourcesNumLoaded++;
        targetAudioBuffer = buffer;
        afterResourcesEnter();
    });
}

let pistolRayCasterCalculate = function () {
    const maxLength = config.playground.playgroundDepth / 2 + config.playground.playgroundWidth
        + config.targetGround.targetWidth + config.targetGround.targetDepth + config.targetGround.targetHeight;

    const cameraWorldDirection = new THREE.Vector3();
    camera.getWorldDirection(cameraWorldDirection);
    const rayCaster = new THREE.Raycaster(camera.position, cameraWorldDirection, 0, maxLength);
    const intersections = rayCaster.intersectObjects(targets.children);

    if (intersections.length) {
        for (let i = 0; i < intersections.length; i++) {
            const targetHit = intersections[i].object
            if (targetHit) {
                targetHit.traverse(function (children) {
                    if (children.name === 'hitAudio') {
                        children.play();
                    }
                })
                targets.remove(targetHit);

                if (camera.position.z <= -quarterLength && camera.position.z > -quarterLength * 2) score += 1
                if (camera.position.z <= 0 && camera.position.z > -quarterLength) score += 2
                if (camera.position.z > 0 && camera.position.z <= quarterLength) score += 3
                if (camera.position.z > quarterLength && camera.position.z <= quarterLength * 2) score += 4
            }
        }
    }
}

let initTargets = function () {
    if (controls.isLocked && targets.children.length <= 20) {
        // targetMesh
        const geometry = new THREE.CircleBufferGeometry(2, 32);
        const material = new THREE.MeshBasicMaterial({color: 0x000000});

        // targetAudio
        const targetAudio = new THREE.PositionalAudio(listener);
        targetAudio.setBuffer(targetAudioBuffer);
        targetAudio.setVolume(15);
        targetAudio.name = 'hitAudio';

        // targetObject
        target = new THREE.Mesh(geometry, material);
        target.position.set(config.targetGround.targetWidth / 2 - Math.random() * config.targetGround.targetWidth, Math.random() * config.targetGround.targetHeight, -config.targetGround.targetDepth);
        target.add(targetAudio);
        targets.add(target);
    }
}

let initController = function () {
    controls = new PointerLockControls(camera, document.body);

    const collimator = document.getElementById('collimator');
    const blocker = document.getElementById('blocker');
    const instructions = document.getElementById('instructions');

    instructions.addEventListener('click', function () {
        controls.lock();
    });

    controls.addEventListener('lock', function () {
        collimator.style.display = 'block';
        instructions.style.display = 'none';
        blocker.style.display = 'none';
    });

    controls.addEventListener('unlock', function () {
        collimator.style.display = '';
        blocker.style.display = 'block';
        instructions.style.display = '';
    });
    scene.add(controls.getObject());

    const onKeyDown = function (event) {
        switch (event.code) {
            case 'ArrowUp':
            case 'KeyW':
                config.keyboardRecord.moveForward = true;
                break;
            case 'ArrowLeft':
            case 'KeyA':
                config.keyboardRecord.moveLeft = true;
                break;
            case 'ArrowDown':
            case 'KeyS':
                config.keyboardRecord.moveBackward = true;
                break;
            case 'ArrowRight':
            case 'KeyD':
                config.keyboardRecord.moveRight = true;
                break;
            case 'Space':
                if (config.keyboardRecord.canJump === true) velocity.y += config.camera.jumpingAcceleration;
                config.keyboardRecord.canJump = false;
                break;
        }
    };

    const onKeyUp = function (event) {
        switch (event.code) {
            case 'ArrowUp':
            case 'KeyW':
                config.keyboardRecord.moveForward = false;
                break;
            case 'ArrowLeft':
            case 'KeyA':
                config.keyboardRecord.moveLeft = false;
                break;
            case 'ArrowDown':
            case 'KeyS':
                config.keyboardRecord.moveBackward = false;
                break;
            case 'ArrowRight':
            case 'KeyD':
                config.keyboardRecord.moveRight = false;
                break;
        }
    };

    const onMouseClick = function () { // 玩家开枪触发
        if (controls.isLocked) { // 如果正在游戏中
            fireTimes++;
            if (!pistolAudio.isPlaying) {
                pistolAudio.play();
            } else {
                pistolAudio.stop();
                pistolAudio.play();
            }
            pistolRayCasterCalculate();
        }
    }

    document.addEventListener('keydown', onKeyDown);
    document.addEventListener('keyup', onKeyUp);
    document.addEventListener('click', onMouseClick);
}

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize(window.innerWidth, window.innerHeight);
}

let playerControlAnimate = function () {
    const delta = clock.getDelta();

    velocity.x -= velocity.x * 10.0 * delta;
    velocity.z -= velocity.z * 10.0 * delta;
    velocity.y -= config.camera.gravity * config.camera.mass * delta;

    direction.z = Number(config.keyboardRecord.moveForward) - Number(config.keyboardRecord.moveBackward);
    direction.x = Number(config.keyboardRecord.moveRight) - Number(config.keyboardRecord.moveLeft);
    direction.normalize(); // this ensures consistent movements in all directions

    if (config.keyboardRecord.moveForward || config.keyboardRecord.moveBackward) velocity.z -= direction.z * 400.0 * delta;
    if (config.keyboardRecord.moveLeft || config.keyboardRecord.moveRight) velocity.x -= direction.x * 400.0 * delta;

    controls.moveRight(-velocity.x * delta);
    controls.moveForward(-velocity.z * delta);
    controls.getObject().position.y += (velocity.y * delta); // new behavior

    if (controls.getObject().position.y < 10) {
        velocity.y = 0;
        controls.getObject().position.y = 10;
        config.keyboardRecord.canJump = true;
    }
}

let playerScoreDashBoardAnimate = function () {
    scoreComponent.innerHTML = score.toString();
    fireComponent.innerHTML = fireTimes.toString();
    const elapsedTime = Math.floor(clock.getElapsedTime() * 10);
    let elapsedTimeFloor = (elapsedTime / 10).toString();
    if (elapsedTime % 10 === 0) elapsedTimeFloor = (elapsedTime / 10) + '.0'
    timeComponent.innerHTML = elapsedTimeFloor;
}

function animate() {
    requestAnimationFrame(animate);

    if (controls.isLocked) { // 如果玩家进入游戏
        playerControlAnimate(); // 判断输入参数事件来控制玩家位移
        playerScoreDashBoardAnimate(); // 按帧率刷新计分板
    }

    renderer.render(scene, camera);
    stats.update();
}

initBasic();
initScene();
initResources();

let afterResourcesEnter = function () {
    if (resourcesNumLoaded === resourcesNum) {
        setInterval(initTargets, 700)
    }
}

initController();
animate();

